package de.htw.noise;

import static java.lang.Math.abs;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import static java.lang.Math.*;

public class PerlinNoiseDemo extends JFrame {
	
	private static enum Modulator {
		NONE, SINE, COSINE, EULER, INVERT;
	}
	
	private Modulator modulator;
	
	final int width,height;
	protected VolatileImage display;
	protected BufferedImage image;
	
	GraphicsConfiguration config;
	PerlinNoise noise;
	
	ThreadPoolExecutor pool;
	BlockingQueue<Runnable> queue;
	private Collection<Callable<Void>> tasks;
	
	private Interpolation2D interpolate;
	int mouseX = 23;
	int mouseY = 56;
	boolean update = false;
	private JPanel pimg;
	private double persistence;
	int octaves;
	short seed;
	
	public PerlinNoiseDemo() throws InterruptedException {
		super("perlin noise demo 1");
		persistence = 0.25;
		octaves = 8;
		seed = 1024;
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		this.modulator = Modulator.NONE;
		this.interpolate = new Interpolation2D();
		this.width = 512;
		this.height = width;
		this.noise = new PerlinNoise(seed, persistence, octaves);
		this.image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		this.queue = new ArrayBlockingQueue<Runnable>(128);
		this.pool = new ThreadPoolExecutor(64, 128, 100L, TimeUnit.MILLISECONDS, queue);
		setPreferredSize(new Dimension(width, 348));
		
		
		final JSlider _seed		= new JSlider(53, Short.MAX_VALUE, seed);
		final JSlider _persistence = new JSlider(1, 100, (int) (persistence * 100));
		final JSlider _octaves		= new JSlider(1, 16, octaves);
		final JSlider _modulator 	= new JSlider(0,Modulator.values().length - 1 , modulator.ordinal());
		
		
		_persistence.setPreferredSize(new Dimension(100,80));
		_octaves.setPreferredSize(new Dimension(100,80));
		_seed.setPreferredSize(new Dimension(100,80));
		_modulator.setPreferredSize(new Dimension(50,80));
		
		JPanel octpanel = new JPanel();
		octpanel.add(_octaves);
		final JLabel octavesLabel = new JLabel("o");
		octpanel.add(octavesLabel);
		octpanel.add(_persistence);
		final JLabel persistenceLabel = new JLabel("p");
		octpanel.add(persistenceLabel);
		octpanel.add(_seed);
		final JLabel seedlabel = new JLabel("s");
		octpanel.add(seedlabel);
		final JLabel modlabel = new JLabel();
		octpanel.add(_modulator);
		octpanel.add(modlabel);
		
		octpanel.setPreferredSize(new Dimension(50,80));
		
		JPanel panel = new JPanel();
		panel.setLayout(new StackLayout());
		panel.add(octpanel, StackLayout.TOP);
		add(panel, BorderLayout.SOUTH);
		
		persistenceLabel.setText("" + persistence);
		seedlabel.setText("" + seed);		
		octavesLabel.setText("" + octaves);
		
		_persistence.addChangeListener(new ChangeListener() {
			
			@Override
			public void stateChanged(ChangeEvent e) {
				persistence = _persistence.getValue() / 100.0;
				noise.update(seed, persistence, octaves);
				update = true;
				persistenceLabel.setText("" + persistence);
			}
		});
		
		_octaves.addChangeListener(new ChangeListener() {
			
			@Override
			public void stateChanged(ChangeEvent e) {
				octaves = _octaves.getValue();
				noise.update(seed, persistence, octaves);
				update = true;
				octavesLabel.setText("" + octaves);
			}
		});
		
		_seed.addChangeListener(new ChangeListener() {
			
			@Override
			public void stateChanged(ChangeEvent e) {
				seed = (short) _seed.getValue();
				noise.update(seed, persistence, octaves);
				update = true;
				seedlabel.setText("" + seed);
			}
		});
		
		_modulator.addChangeListener(new ChangeListener() {
			
			@Override
			public void stateChanged(ChangeEvent e) {
				int val = _modulator.getValue();
				modulator = Modulator.values()[val];
				modlabel.setText("" + modulator);
				update = true;
			}
		});
		
		setResizable(false);
		setVisible(true);
		pack();
		setLocationRelativeTo(null);
		
		pimg = new JPanel();
		pimg.setPreferredSize(new Dimension(getPreferredSize()));
		
		pimg.addMouseMotionListener(new MouseAdapter() {
			
			double oldx,oldy;
			
			@Override
			public void mouseDragged(MouseEvent e) {
				double dx = abs(oldx - e.getX()) / (double)width;
				double dy = abs(oldy - e.getY()) / (double)height;
				
				persistence = dx;
				octaves = (int) (dy * 32);
				update = true;
			}
			
			@Override
			public void mouseMoved(MouseEvent e) {
				oldx = e.getX();
				oldy = e.getY();
			}
		});
		
		add(pimg, BorderLayout.CENTER);
		
	
		final int numThreads = 128;
		final int step = width / numThreads;
		tasks = new ArrayList<Callable<Void>>();
		
		for(int i = 1; i <= numThreads; i++)
		{
			final int start = step * i;
			final int end 	= start + step;
			
			Callable<Void> callable = new Callable<Void>() {
				
				@Override
				public Void call() throws Exception {
					render(start, end);
					return null;
				}
			};
			tasks.add(callable);
		}
		pool.invokeAll(tasks);
		
		
	}
	
	public void render(final int start, final int end) throws Exception{
		while (true) {
			if (update) {
				synchronized (image) {
					for (int i = start; i < end; i++)
					for (int j = 1; j < height - 1; j++) {
							double scaleX = width;
							double scaleY = height;
							
							double n = (noise.perlinNoise2D(i / scaleX, j / scaleY));
							
							switch(modulator)
							{
								case SINE:
									n = abs(sin(n * i));
									break;
								case COSINE:
									n = abs(cos(n * i));
									break;
								case EULER:
									n = abs(pow(E, n));
									break;
								case INVERT:
									n = 1.0 - n;
									break;
							}
							
							int luma = 0;

							int re = (int) (127.0 * n) + luma;
							int gr = (int) (63.0 * n) + luma;
							int bl = (int) (31.0 * n) + luma;
							int clr = (re) << 16 | (gr) << 8 | (bl);
							int x = j;
							int y = i;
							x = (x >= width) ? (width - 1) : (x < 0) ? 0 : x;
							y = (y >= height) ? (height - 1) : (y < 0) ? 0 : y;

							image.setRGB(x, y, clr);
						}
					pimg.getGraphics().drawImage(image, 0, 0, null);
				} // end synchronizer block
				update = false;
			}
			Thread.sleep(1);
		}
	}
	
	@Override
	public void paint(Graphics g) {
		super.paint(g);
		pimg.getGraphics().drawImage(image, 0, 0, null);
	}
	
	public static void main(String[] args) throws InterruptedException {
		new PerlinNoiseDemo();
	}
}
